Skip to content

fix buffer overflow on all emphasis flags set - #4498

Merged
vitaut merged 2 commits into
fmtlib:masterfrom
dominicpoeschko:master
Jul 20, 2025
Merged

fix buffer overflow on all emphasis flags set#4498
vitaut merged 2 commits into
fmtlib:masterfrom
dominicpoeschko:master

Conversation

@dominicpoeschko

@dominicpoeschko dominicpoeschko commented Jul 15, 2025

Copy link
Copy Markdown
Contributor

Found while fuzzing some code.
The following code compiled with sanitizer overflows a buffer in fmt::detail::ansi_color_escape.

g++ -fsanitize=address main.cpp
#define FMT_HEADER_ONLY

#include <fmt/color.h>

int main(){

    fmt::text_style ts{};

    ts |= fmt::emphasis::bold;
    ts |= fmt::emphasis::faint;
    ts |= fmt::emphasis::italic;
    ts |= fmt::emphasis::underline;
    ts |= fmt::emphasis::blink;
    ts |= fmt::emphasis::reverse;
    ts |= fmt::emphasis::conceal;
    ts |= fmt::emphasis::strikethrough;

    fmt::print("{}", fmt::styled("test", ts));
}
=================================================================
==1064648==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b5f2a0004a0 at pc 0x56231c89794d bp 0x7fff64601a90 sp 0x7fff64601a80
WRITE of size 1 at 0x7b5f2a0004a0 thread T0
    #0 0x56231c89794c in fmt::v11::detail::ansi_color_escape<char>::ansi_color_escape(fmt::v11::emphasis) (/home/dominic/tmp/fmt_overflow/a.out+0x2494c) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #1 0x56231c88fc0c in fmt::v11::detail::ansi_color_escape<char> fmt::v11::detail::make_emphasis<char>(fmt::v11::emphasis) (/home/dominic/tmp/fmt_overflow/a.out+0x1cc0c) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #2 0x56231c8b3b36 in decltype (({parm#2}.out)()) fmt::v11::formatter<fmt::v11::detail::styled_arg<char [5]>, char, void>::format<fmt::v11::context>(fmt::v11::detail::styled_arg<char [5]> const&, fmt::v11::context&) const (/home/dominic/tmp/fmt_overflow/a.out+0x40b36) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #3 0x56231c8a09ff in void fmt::v11::detail::value<fmt::v11::context>::format_custom<fmt::v11::detail::styled_arg<char [5]>, fmt::v11::formatter<fmt::v11::detail::styled_arg<char [5]>, char, void> >(void*, fmt::v11::parse_context<char>&, fmt::v11::context&) (/home/dominic/tmp/fmt_overflow/a.out+0x2d9ff) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #4 0x56231c895114 in fmt::v11::basic_format_arg<fmt::v11::context>::handle::format(fmt::v11::parse_context<char>&, fmt::v11::context&) const (/home/dominic/tmp/fmt_overflow/a.out+0x22114) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #5 0x56231c88f524 in fmt::v11::detail::default_arg_formatter<char>::operator()(fmt::v11::basic_format_arg<fmt::v11::context>::handle) (/home/dominic/tmp/fmt_overflow/a.out+0x1c524) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #6 0x56231c882ffb in fmt::v11::detail::vformat_to(fmt::v11::detail::buffer<char>&, fmt::v11::basic_string_view<char>, fmt::v11::basic_format_args<fmt::v11::context>, fmt::v11::detail::locale_ref) (/home/dominic/tmp/fmt_overflow/a.out+0xfffb) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #7 0x56231c883779 in fmt::v11::vprint(_IO_FILE*, fmt::v11::basic_string_view<char>, fmt::v11::basic_format_args<fmt::v11::context>) (/home/dominic/tmp/fmt_overflow/a.out+0x10779) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #8 0x56231c883905 in fmt::v11::vprint(fmt::v11::basic_string_view<char>, fmt::v11::basic_format_args<fmt::v11::context>) (/home/dominic/tmp/fmt_overflow/a.out+0x10905) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #9 0x56231c878cc9 in main (/home/dominic/tmp/fmt_overflow/a.out+0x5cc9) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)
    #10 0x7f5f2c0376b4  (/usr/lib/libc.so.6+0x276b4) (BuildId: 468e3585c794491a48ea75fceb9e4d6b1464fc35)
    #11 0x7f5f2c037768 in __libc_start_main (/usr/lib/libc.so.6+0x27768) (BuildId: 468e3585c794491a48ea75fceb9e4d6b1464fc35)
    #12 0x56231c878524 in _start (/home/dominic/tmp/fmt_overflow/a.out+0x5524) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)

Address 0x7b5f2a0004a0 is located in stack of thread T0 at offset 160 in frame
    #0 0x56231c8b39d3 in decltype (({parm#2}.out)()) fmt::v11::formatter<fmt::v11::detail::styled_arg<char [5]>, char, void>::format<fmt::v11::context>(fmt::v11::detail::styled_arg<char [5]> const&, fmt::v11::context&) const (/home/dominic/tmp/fmt_overflow/a.out+0x409d3) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec)

  This frame has 6 object(s):
    [32, 40) 'out' (line 589)
    [64, 80) '<unknown>'
    [96, 112) 'reset_color' (line 611)
    [128, 160) 'emphasis' (line 594) <== Memory access at offset 160 overflows this variable
    [192, 224) 'foreground' (line 599)
    [256, 288) 'background' (line 605)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/dominic/tmp/fmt_overflow/a.out+0x2494c) (BuildId: 3cbd94becf314d362227e35ad13b65a34a8d0eec) in fmt::v11::detail::ansi_color_escape<char>::ansi_color_escape(fmt::v11::emphasis)
Shadow bytes around the buggy address:
  0x7b5f2a000200: f1 f1 f1 f1 f1 f1 01 f2 00 f2 f2 f2 00 f2 f2 f2
  0x7b5f2a000280: 00 00 f2 f2 00 00 f2 f2 00 00 f2 f2 00 00 00 00
  0x7b5f2a000300: f2 f2 f2 f2 00 00 00 00 00 00 00 f3 f3 f3 f3 f3
  0x7b5f2a000380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b5f2a000400: f1 f1 f1 f1 00 f2 f2 f2 00 00 f2 f2 00 00 f2 f2
=>0x7b5f2a000480: 00 00 00 00[f2]f2 f2 f2 00 00 00 00 f2 f2 f2 f2
  0x7b5f2a000500: 00 00 00 00 f3 f3 f3 f3 00 00 00 00 00 00 00 00
  0x7b5f2a000580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b5f2a000600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b5f2a000680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b5f2a000700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==1064648==ABORTING
gcc version 15.1.1 20250425 (GCC)

fmt version latest commit as of writing. git commit hash 553ec11

@vitaut vitaut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the fix! Could you add a test case to color-test?

@dominicpoeschko

Copy link
Copy Markdown
Contributor Author

Yes, I hope to find time for it at the weekend.

@dominicpoeschko
dominicpoeschko requested a review from vitaut July 18, 2025 11:11

@vitaut vitaut left a comment